using System;
using System.Collections.Generic;

namespace Atomic.Thermodynamics
{
	[Serializable]
	public abstract class Potential : IPotential
	{
		public abstract double FreeEnergy(double temperature);

		public abstract double Entropy(double temperature);

		public abstract double HeatCapacity(double temperature);

		public Potential Scale(double factor)
		{
			return Scale(this, factor);
		}

		public static Potential Scale(IPotential potential, double factor)
		{
			return new ScaledPotential(potential, factor);
		}

		public static Potential Create(IPotential potential)
		{
			return Add(potential);
		}

		public static Potential Add(IEnumerable<IPotential> potentials)
		{
			return new AddedPotential(potentials);
		}

		public static Potential Add(params IPotential[] potentials)
		{
			return Add((IEnumerable<IPotential>)potentials);
		}

		public static Potential Constant(double value)
		{
			return new ConstantPotential(value);
		}
		
		[Serializable]
		private class ScaledPotential : Potential
		{
			private IPotential potential;
			private double factor;

			public ScaledPotential(IPotential potential, double factor)
			{
				this.potential = potential;
				this.factor = factor;
			}

			public override double FreeEnergy(double temperature)
			{
				return potential.FreeEnergy(temperature) * factor;
			}

			public override double Entropy(double temperature)
			{
				return potential.Entropy(temperature) * factor;
			}

			public override double HeatCapacity(double temperature)
			{
				return potential.HeatCapacity(temperature) * factor;
			}
		}

		[Serializable]
		private class AddedPotential : Potential
		{
			private List<IPotential> potentials;

			public AddedPotential(IEnumerable<IPotential> potentials)
			{
				this.potentials = new List<IPotential>(potentials);
			}

			public override double FreeEnergy(double temperature)
			{
				double f = 0.0;
				foreach (IPotential potential in potentials)
				{
					f += potential.FreeEnergy(temperature);
				}

				return f;
			}

			public override double Entropy(double temperature)
			{
				double s = 0.0;
				foreach (IPotential potential in potentials)
				{
					s += potential.Entropy(temperature);
				}

				return s;
			}

			public override double HeatCapacity(double temperature)
			{
				double cv = 0.0;
				foreach (IPotential potential in potentials)
				{
					cv += potential.HeatCapacity(temperature);
				}

				return cv;
			}
		}

		[Serializable]
		private class ConstantPotential : Potential
		{
			private double value;

			public ConstantPotential(double value)
			{
				this.value = value;
			}

			public override double FreeEnergy(double temperature)
			{
				return value;
			}

			public override double Entropy(double temperature)
			{
				return 0.0;
			}

			public override double HeatCapacity(double temperature)
			{
				return 0.0;
			}
		}
	}
}
